一、RAII:资源获取即初始化 RAII(Resource Acquisition Is Initialization)是 C++ 最重要的惯用法之一。核心思想是:将资源的生命周期与对象的生命周期绑定——构造函数获取资源,析构函数释放资源 。这确保了即使在异常路径下,资源也能被正确释放。
Android 的 AOSP 代码中大量使用 RAII。以 Binder 驱动的锁管理为例:
class Mutex {public : int lock () ; void unlock () ; }; class AutoMutex {public : AutoMutex (Mutex& m) : mLock (m) { mLock.lock (); } ~AutoMutex () { mLock.unlock (); } private : Mutex& mLock; }; void IPCThreadState::waitForResponse (Parcel *reply, status_t *acquireResult) { AutoMutex _l(mProcess->mThreadCountLock); }
源码路径:frameworks/native/libs/binder/include/binder/Mutex.h
1.1 智能指针(Smart Pointers) C++11 标准库提供了三种智能指针,Android AOSP 还有自己的实现(sp/wp 即 StrongPointer/WeakPointer,基于轻量级引用计数)。
shared_ptr :共享所有权,引用计数为 0 时释放资源。
#include <memory> class Connection {public : Connection (const std::string& addr) : m_addr (addr) { } ~Connection () { } private : std::string m_addr; }; void processRequest () { auto conn = std::make_shared <Connection>("192.168.1.1:8080" ); doSomething (conn); }
unique_ptr :独占所有权,不可拷贝,可移动。零开销(与裸指针性能相同)。
std::unique_ptr<AudioTrack> createAudioTrack (int sampleRate) { auto track = std::make_unique <AudioTrack>(sampleRate); if (!track->init ()) { return nullptr ; } return track; }
weak_ptr :弱引用,不增加引用计数,用于打破 shared_ptr 的循环引用。
class Node { std::string name; std::vector<std::shared_ptr<Node>> children; std::weak_ptr<Node> parent; public : Node (const std::string& n) : name (n) {} void setParent (std::shared_ptr<Node> p) { parent = p; } };
1.2 Android AOSP 的 sp/wp Android 的 Binder IPC 体系使用 sp<T>(强指针)和 wp<T>(弱指针)管理引用计数:
class RefBase {public : void incStrong (const void * id) const ; void decStrong (const void * id) const ; }; template <typename T>class sp { T* m_ptr; public : sp (T* other) : m_ptr (other) { if (m_ptr) m_ptr->incStrong (this ); } ~sp () { if (m_ptr) m_ptr->decStrong (this ); } };
IBinder、BnInterface、BpInterface 等 Binder 核心类都继承自 RefBase,通过 sp 管理生命周期。
二、移动语义与右值引用 C++11 的移动语义是一个关键优化:对于临时对象,可以将资源”移动”而不是”拷贝”,避免不必要的内存分配。
class Buffer { char * m_data; size_t m_size; public : Buffer (Buffer&& other) noexcept : m_data (other.m_data), m_size (other.m_size) { other.m_data = nullptr ; other.m_size = 0 ; } Buffer& operator =(Buffer&& other) noexcept { if (this != &other) { delete [] m_data; m_data = other.m_data; m_size = other.m_size; other.m_data = nullptr ; other.m_size = 0 ; } return *this ; } Buffer (const Buffer&) = delete ; Buffer& operator =(const Buffer&) = delete ; };
在 Android NDK 开发中,大 Buffer(如相机帧数据、音频数据)通过移动语义传递可以避免不必要的数据拷贝,显著提升性能。
std::vector<uint8_t > frame1 = captureFrame (); std::vector<uint8_t > frame2 = std::move (frame1);
std::forward 用于完美转发,保持参数的左值/右值属性:
template <typename T>void wrapper (T&& arg) { target (std::forward<T>(arg)); }
三、模板元编程 C++ 模板不仅是泛型编程的工具,还是一种编译期计算语言。Android 的 HAL(硬件抽象层)和 ART 运行时大量使用模板提升性能和复用。
3.1 SFINAE(替换失败不是错误) SFINAE 规则是模板元编程的基础:当模板参数替换失败时,编译器不会报错,而是从重载集中排除该模板。
template <typename T>class has_foo {private : typedef char yes[1 ]; typedef char no[2 ]; template <typename U, U> struct type_check ; template <typename C> static yes& test (type_check<void (C::*)(), &C::foo>*) ; template <typename > static no& test (...) ;public : static constexpr bool value = (sizeof (test <T>(nullptr )) == sizeof (yes)); };
C++17 引入 if constexpr 后,条件编译变得更简洁:
template <typename T>void serialize (std::vector<uint8_t >& buffer, const T& value) { if constexpr (std::is_integral_v<T>) { buffer.insert (buffer.end (), reinterpret_cast <const uint8_t *>(&value), reinterpret_cast <const uint8_t *>(&value) + sizeof (T)); } else if constexpr (std::is_same_v<T, std::string>) { uint32_t len = value.size (); serialize (buffer, len); buffer.insert (buffer.end (), value.begin (), value.end ()); } else { static_assert (sizeof (T) == 0 , "Unsupported type for serialization" ); } }
3.2 Type Traits 标准库的 type_traits 头文件提供了大量编译期类型判断和转换的工具:
#include <type_traits> static_assert (std::is_same_v<int , int32_t >); static_assert (std::is_base_of_v<Base, Derived>); static_assert (std::is_constructible_v<MyClass, int , double >); using Decayed = std::decay_t <const int &>; using Underlying = std::underlying_type_t <EnumClass>;
在 Android NDK 中的实际应用——类型安全的资源管理:
template <typename T>class sp { static_assert (std::is_base_of_v<RefBase, T>, "sp<T>: T must be a subclass of RefBase" ); };
四、Lambda 表达式 C++ lambda 是 Android NDK 开发中最常用的语法糖之一,广泛应用于回调、线程任务、STL 算法等场景。
auto byValue = [value]() { return value * 2 ; }; auto byRef = [&value]() { value *= 2 ; }; auto all = [=]() { return x + y + z; }; auto allRef = [&]() { x = y = z = 0 ; }; auto mixed = [=, &result]() { result = a + b; }; std::thread task ([buffer = std::move(buffer)]() { processBuffer(buffer); }) ;task.join (); std::vector<int > values = {5 , 3 , 1 , 4 , 2 }; std::sort (values.begin (), values.end (), [](int a, int b) { return a > b; }); auto it = std::find_if (values.begin (), values.end (), [threshold = 3 ](int x) { return x > threshold; });
注意:lambda 捕获引用时,必须确保 lambda 执行时引用仍然有效:
std::function<void () > createBadCallback () { int local = 42 ; return [&local]() { std::cout << local; }; } std::function<void () > createGoodCallback () { int local = 42 ; return [local]() { std::cout << local; }; }
五、STL 容器选择指南 Android NDK 开发中的容器选择直接影响性能。以下是关键容器的特性对比和选择建议:
容器
底层
随机访问
插入/删除
内存
适用场景
vector
动态数组
O(1)
O(n)
连续
默认首选,尾部操作
deque
分段数组
O(1)
O(1) 两端
分段
双端队列
list
双向链表
O(n)
O(1)
分散,每元素额外 2 指针
大量中间插入/删除
set/map
红黑树
O(log n)
O(log n)
分散
有序集合/映射
unordered_set/map
哈希表
O(1) 均摊
O(1) 均摊
分散,有桶开销
快速查找,不关心顺序
std::vector<Pixel> scanline; scanline.reserve (1920 ); std::unordered_map<std::string, std::unique_ptr<Plugin>> pluginRegistry; auto it = pluginRegistry.find ("video_decoder" );std::map<uint32_t , std::string> sortedLogs;
六、线程安全与互斥锁 #include <mutex> #include <shared_mutex> class ThreadSafeCache { mutable std::shared_mutex m_mutex; std::unordered_map<int , Data> m_cache; public : void update (int key, Data value) { std::unique_lock<std::shared_mutex> lock (m_mutex) ; m_cache[key] = std::move (value); } std::optional<Data> get (int key) const { std::shared_lock<std::shared_mutex> lock (m_mutex) ; auto it = m_cache.find (key); if (it != m_cache.end ()) { return it->second; } return std::nullopt ; } }; std::mutex mtx1, mtx2; void safeTransfer () { std::scoped_lock lock (mtx1, mtx2) ; } class Singleton { static std::unique_ptr<Singleton> s_instance; static std::once_flag s_flag; public : static Singleton& getInstance () { std::call_once (s_flag, []() { s_instance.reset (new Singleton ()); }); return *s_instance; } };
源码参考:AOSP 的 system/core/libutils/include/utils/Mutex.h 中,Android 使用 pthread_mutex_t 而非 std::mutex(因为 AOSP 代码长期基于 C++11 之前的标准)。
七、const 正确性 const 是 C++ 类型系统的核心部分,不仅是”不可变”的承诺,也帮助编译器优化和防止 bug:
class AudioBuffer { int16_t * m_data; size_t m_size; public : size_t size () const { return m_size; } const int16_t * data () const { return m_data; } int16_t * data () { return m_data; } void append (const AudioBuffer& other) ; };
八、面试常问题目 Q1: shared_ptr 和 unique_ptr 的区别?什么场景用哪个?
unique_ptr 独占所有权,不能拷贝(只能移动),编译后与裸指针性能相同(零开销)。适合工厂函数返回、容器中管理对象、PIMPL 惯用法。shared_ptr 共享所有权,有引用计数开销(原子操作),适合多个所有者需要共享同一对象的场景。原则:优先用 unique_ptr,只有确实需要共享所有权时才用 shared_ptr。
Q2: 移动语义解决了什么问题?std::move 做了什么?
移动语义解决了不必要的深拷贝开销——对于包含动态分配资源的对象(如 std::vector、std::string),”移动”只需要复制指针(O(1)),而”拷贝”需要复制整个数据(O(n))。std::move 是一个无条件将左值转换为右值引用的类型转换(static_cast),它本身不移动任何东西,只是让编译器选择移动构造函数/移动赋值运算符而不是拷贝版本。
Q3: std::mutex 和 std::shared_mutex 的区别?
std::mutex 是排他锁(互斥锁),同一时刻只有一个线程能获取锁。std::shared_mutex 支持两种锁定模式:shared_lock(读锁,允许并发读)和 unique_lock(写锁,独占)。在读多写少的场景(如缓存访问),shared_mutex 可显著提高并发性能。Android 中 android:hardware:details:utils 命名空间下有类似实现。
Q4: 为什么在 Android NDK 中 vector 通常优于 list?
虽然 list 的理论插入/删除复杂度是 O(1)(vs vector 的 O(n)),但现代 CPU 的缓存架构使得连续内存的访问远快于指针跳转。vector 在连续内存中存储元素,CPU 预取(prefetch)可以高效工作;list 的每个节点分散在堆上,每次跳转都可能造成 cache miss。除非元素非常大(如 > 1KB)且频繁在中间插入/删除,否则 vector 通常更快。
参考源码路径:
AOSP RefBase:system/core/libutils/include/utils/RefBase.h
AOSP StrongPointer:system/core/libutils/include/utils/StrongPointer.h
AOSP Mutex:system/core/libutils/include/utils/Mutex.h
C++ Standard Library:https://en.cppreference.com/w/
Android NDK 文档:https://developer.android.com/ndk/guides/cpp-support